# B站视频编号(aid)和BV号(bvid)相互转换工具
# 参考链接：https://socialsisteryi.github.io/bilibili-API-collect/docs/misc/bvid_desc.html

# XOR 运算密钥，用于 aid 与中间值的异或转换
XOR_CODE = 23442827791579
# 掩码值，用于 bvid 转 aid 时提取有效位
MASK_CODE = 2251799813685247
# aid 的最大值，用于确保转换钱的数值位数
MAX_AID = 1 << 51  # 2^51

# BV号使用的字符集，共62个字符（大小写字母，数字）
ALPHABET = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf"

# 编码映射表，用于确定 aid 转换为 bvid 时，各个字符的的位置
ENCODE_MAP = 8, 7, 0, 5, 1, 3, 2, 4, 6

# 反转的编码映射表，用于 bvid 转 aid 时，解析各个字符的位置
DECODE_MAP = tuple(reversed(ENCODE_MAP))

# 字符集长度，用于作为进制转换的基数
BASE = len(ALPHABET)

# BV号的固定前缀
PREFIX = "BV1"
# BV号前缀长度
PREFIX_LEN = len(PREFIX)
# 编码长度，BV号前缀后包含的字符数量
CODE_LEN = len(ENCODE_MAP)


def av2bv(aid: int) -> str:
    """
    将 aid 转换为 bvid

    参数:
        aid: 视频的数字编号

    返回:
        bvid: 对应的BV号
    """
    # 初始化BV号字符列表（固定前缀 + 9位编码）
    bvid = [""] * 9
    # 计算中间值：将 aid 与 MAX_AID 进行或运算后，在与 XOR_CODE 异或
    tmp = (MAX_AID | aid) ^ XOR_CODE

    # 循环计算每个位置的字符
    for i in range(CODE_LEN):
        # 根据编码映射表确定当前字符的位置
        bvid[ENCODE_MAP[i]] = ALPHABET[tmp % BASE]
        # 整数除法，处理下一位
        tmp //= BASE

    # 拼接前缀和编码部分，形成完整的 BV 号
    return PREFIX + "".join(bvid)


def bv2av(bvid: str) -> int:
    """
    将 bvid 转换为 aid

    参数:
        bvid: 视频的BV号

    返回:
        aid: 对应的视频编号

    """
    # 验证BV号前缀是否正确
    if bvid[:3] != PREFIX:
        raise ValueError(f"无效的BV号，必须以'{PREFIX}'开头，实际为：{bvid[:3]}")

    # 提取BV号中的编码部分（去除前缀）
    bvid = bvid[3:]
    tmp = 0

    # 循环解析每个字符，计算中间值
    for i in range(CODE_LEN):
        # 根据解码映射表获取字符位置，并查找其在字符集中的索引
        idx = ALPHABET.index(bvid[DECODE_MAP[i]])
        # 计算中间值（类似进制转换）
        tmp = tmp * BASE + idx

    # 中间值与掩码运算后在与 XOR_CODE 异或，得到最终的 aid
    return (tmp & MASK_CODE) ^ XOR_CODE
